package com.fasteat.menu.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasteat.common.entity.GlobalException;
import com.fasteat.common.entity.Result;
import com.fasteat.common.entity.StatusCode;
import com.fasteat.menu.async.RecommendAsync;
import com.fasteat.menu.entity.vo.AuthorVo;
import com.fasteat.menu.entity.vo.MenuVo;
import com.fasteat.menu.mapper.MenuMapper;
import com.fasteat.menu.service.RecommendService;
import com.fasteat.menu.service.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author run
 * @since 2021/3/2 0:09
 */
@Service
public class RecommendServiceImpl implements RecommendService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private RecommendAsync recommendAsync;

    @Autowired
    private UserClient userClient;

    @Value("${spring.redis.expire-time}")
    private String expireTime;

    @Value("${spring.redis.keyword-key}")
    private String keywordKey;

    @Value("${spring.redis.menu-key}")
    private String menuKey;

    @Scheduled(cron = "0 0 * * * ?")
    public void updateRecommendKeyword(){
        //获得所有用户的推荐信息
        Map map = parsePython("Recommend.py");

        map.forEach((key,value) -> {
            //只更新redis存在的key
            if (!redisTemplate.hasKey(keywordKey + key)){
                return;
            }
            List<String> menuIds = JSON.parseArray(value.toString(), String.class);
            //获取推荐标签
            List<String> tags = CollectionUtils.isEmpty(menuIds) ? new ArrayList<>(0)
                                                                : menuMapper.getTagsOfMenus(menuIds);

            Set<String> tagSet = new HashSet<>();
            tags.forEach((tag) -> {
                if (!tag.isEmpty()){
                    tagSet.addAll(Arrays.asList(tag.split(",")));
                }
            });

            //如果推荐列表不为空，存入redis中
            if (!tagSet.isEmpty()){
                String redisKey = keywordKey + key;
                redisTemplate.opsForSet().add(redisKey,tagSet.toArray());
                redisTemplate.expire(redisKey,Long.valueOf(expireTime), TimeUnit.SECONDS);
            }
        });

        //默认推荐标签
        List<String> hotTags = menuMapper.getHotTags();
        Set<String> tagSet = new HashSet<>();
        hotTags.forEach((tag) -> tagSet.addAll(Arrays.asList(tag.split(","))));
        redisTemplate.opsForSet().add(keywordKey + "default",tagSet.toArray());
    }

    @Scheduled(cron = "0 30 * * * ?")
    public void updateRecommendMenu(){
        //获得所有用户的推荐信息
        Map map = parsePython("Recommend.py");
        map.forEach((key, value)->{
            String redisKey = menuKey + key;

            //只更新redis存在的key
            if (!redisTemplate.hasKey(redisKey)){
                return;
            }


            List<String> menuIds = JSON.parseArray(value.toString(), String.class);

            //清空list
            redisTemplate.opsForList().getOperations().expire(redisKey,0,TimeUnit.MILLISECONDS);

            redisTemplate.opsForList().leftPushAll(redisKey, menuIds);
        });

    }


    public void updateRecommendMenu(String userId){

        String redisKey = menuKey + userId;
        Map map = parsePython("Recommend.py");

        if (!map.containsKey(userId)){
            return;
        }

        redisTemplate.opsForList().leftPushAll(redisKey,JSON.parseArray(map.get(userId).toString(), String.class));
    }

    @Override
    public List<String> getRecommendKeywordOfUser(String userId) {
        String redisKey = keywordKey + userId;

        //如果redis没有缓存该用户的推荐
        if (!redisTemplate.hasKey(redisKey)){
            recommendAsync.insertKeyInRedis(userId);
            List defaultTags = new ArrayList<>(redisTemplate.opsForSet().members(keywordKey + "default"));
            return defaultTags.size() > 10 ? defaultTags.subList(0,10) : defaultTags;
        }

        Set<String> tags = removeEmptyEle(redisTemplate.opsForSet().members(redisKey));

        if (tags.size() >= 10){
            return new ArrayList<>(tags).subList(0,10);
        }

        Set<String> defaultTags = redisTemplate.opsForSet().members(keywordKey + "default");

        if (defaultTags == null){
            return new ArrayList<>(tags);
        }

        defaultTags.forEach((defaultTag)->{
            if (tags.size() < 10 && StrUtil.isNotEmpty(defaultTag)){
                tags.add(defaultTag);
            }
        });

        return new ArrayList<>(tags);
    }

    private static String parse(String json){
        char[] charArray = json.toCharArray();
        for (int i = 1; i < charArray.length - 1; i++) {
            switch (charArray[i]){
                case '{': charArray[i] = '[';break;
                case '}': charArray[i] = ']';break;
                case '\'': charArray[i] = '"';break;
                case 's': charArray[i] = '\"';
                    charArray[++i] = ' ';
                    charArray[++i] = ' ';
                    charArray[++i] = ' ';
                    charArray[++i] = '\"';
                default: break;
            }
        }
        return String.valueOf(charArray);
    }

    private Map parsePython(String fileName){
        String classpath = this.getClass().getResource("/").getPath().substring(1);
        String result = new String();
        try {
            String command = new StringBuilder().append("python ")
                                                .append(classpath)
                                                .append("../classes/")
                                                .append(fileName)
                                                .toString();
            // 执行py文件
            Process proc = Runtime.getRuntime().exec(command);
            //用输入输出流来截取结果
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            result = parse(in.readLine());
            in.close();
            proc.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //获得所有用户的推荐信息
        return JSON.parseObject(result, Map.class);
    }

    @Override
    public List<MenuVo> getRecommendList(String userId, Integer page, Integer size) {
        if (page <= 0 || size <= 0){
            throw new GlobalException(StatusCode.ERROR,"分页参数错误");
        }

        if (StrUtil.isEmpty(userId == null ? null : userId.trim())){
            return setAuthorMessageToMenu(menuMapper.getDefaultRecommendMenu(page,size));
        }

        String redisKey = menuKey + userId;
        if (!redisTemplate.hasKey(redisKey)){
            updateRecommendMenu(userId);
        }

        int startIndex = ((page - 1) * size) % 50;
        int endIndex = (page * size) % 50;
        List<String> menuIds;
        if (startIndex > endIndex){
            List after = redisTemplate.opsForList().range(redisKey, startIndex, 50);
            List pre = redisTemplate.opsForList().range(redisKey, 0, endIndex);
            after.addAll(pre);
            menuIds = after;
        }else {
            menuIds = redisTemplate.opsForList().range(redisKey,startIndex,endIndex);
        }

        //设置过期失效
        redisTemplate.expire(redisKey, Long.valueOf(expireTime), TimeUnit.SECONDS);

        List<MenuVo> menuVos = menuMapper.getMenuVosOfIds(menuIds);
        if (menuVos.isEmpty()){
            return menuVos;
        }

        return setAuthorMessageToMenu(menuVos);
    }

    private List<MenuVo> setAuthorMessageToMenu(List<MenuVo> menuVos){
        Result resultOfUser = userClient.getAuthorVos(
                new ArrayList<>(menuVos.stream().map(MenuVo::getAuthorId).collect(Collectors.toSet())));
        if (resultOfUser.getStatusCode() != StatusCode.OK.getCode()){
            throw new GlobalException(StatusCode.ERROR,"获取失败");
        }

        Map<String, AuthorVo> authorVos = JSON.parseArray(JSON.toJSONString(resultOfUser.getData()), AuthorVo.class)
                .stream()
                .collect(Collectors.toMap(AuthorVo::getAuthorId, authorVo -> authorVo));

        //查询不到菜谱作者信息时的默认对象
        AuthorVo defaultAuthorVo = new AuthorVo();
        defaultAuthorVo.setAuthorName("该用户状态异常");

        menuVos.forEach(menuVo -> {
            AuthorVo authorVo = authorVos.getOrDefault(menuVo.getAuthorId(),defaultAuthorVo);
            menuVo.setAuthorName(authorVo.getAuthorName());
            menuVo.setAuthorAvatar(authorVo.getAuthorAvatar());
        });

        return menuVos;
    }

    private static Set<String> removeEmptyEle(Set<String> set){
        Set<String> result = new HashSet<>();
        set.forEach((ele)->{
            if (StrUtil.isNotEmpty(ele)){
                result.add(ele);
            }
        });
        return result;
    }
}
